#include <linux/errno.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/sched.h>
#include <linux/seq_file.h>
#include <linux/slab.h>
#include <linux/wait.h>
#include <asm/uaccess.h>

/***************************************************************/
/************************** Debug ******************************/
/***************************************************************/
/*
 * Error messages
 */
#define print_failed_exit(func)					\
	pr_err_once("FAILED:%s()\n", func);
#define print_error(...)					\
	do {							\
		pr_err_once("ERROR:");				\
		printk(__VA_ARGS__);				\
		printk("\n");					\
	} while (0)
/*
 * LOG messages
 * To activate them add -DDEBUG flag
 */
#define print_start(func)					\
	pr_debug("-->%s()\n", func);
#define print_exit(func)					\
	pr_debug("<--%s()\n", func);
#define print_log(...)						\
	do {							\
		pr_debug("LOG: ");				\
		pr_debug(__VA_ARGS__);				\
		pr_debug("\n");					\
	} while (0)

/***************************************************************/
/****************** Init/destroy functions *********************/
/***************************************************************/
static int __init rwbuffer_init(void);
static void __exit rwbuffer_exit(void);

/***************************************************************/
/*********************** /proc/read ****************************/
/***************************************************************/
#define PROC_READ_NAME		"read"
static struct proc_dir_entry	*proc_read;
/*
 * Open buffer for reading
 */
static int rbuffer_open(struct inode *, struct file *);
/*
 * Read from buffer
 */
static ssize_t rbuffer_read(struct file *, char __user *, size_t, loff_t *);
/*
 * Available file operations
 */
static const struct file_operations rbuffer_fops = {
	.owner		= THIS_MODULE,
	.open		= rbuffer_open,
	.read		= rbuffer_read,
	.llseek		= seq_lseek,
	.release	= seq_release
};
/*
 * seq_file interface
 */
static void *rbuffer_seq_start(struct seq_file *, loff_t *);
static void *rbuffer_seq_next(struct seq_file *, void *, loff_t *);
static void rbuffer_seq_stop(struct seq_file *, void *);
static int rbuffer_seq_show(struct seq_file *, void *);
/*
 * seq_file operations
 */
static const struct seq_operations rbuffer_seq_ops = {
	.start		= rbuffer_seq_start,
	.next		= rbuffer_seq_next,
	.show		= rbuffer_seq_show,
	.stop		= rbuffer_seq_stop
};

/***************************************************************/
/*********************** /proc/write ***************************/
/***************************************************************/
#define PROC_WRITE_NAME		"write"
static struct proc_dir_entry	*proc_write;
/*
 * Write data in buffer
 */
static ssize_t wbuffer_write(struct file *, __user const char *, size_t,
			     loff_t *);
/*
 * Release buffer for what it's worth
 */
static int wbuffer_release(struct inode *, struct file *);
/*
 * Available file operations
 */
static const struct file_operations wbuffer_fops = {
	.owner		= THIS_MODULE,
	.write		= wbuffer_write,
	.release	= wbuffer_release
};

/***************************************************************/
/************************* Wait queue **************************/
/***************************************************************/
static wait_queue_head_t	rwbuffer_wq;
#define	READ_TIMEOUT		15000 /* One minute in jiffies */
/*#define	READ_TIMEOUT		2000 // If you're a very busy man */

/***************************************************************/
/*************************** Buffer ****************************/
/***************************************************************/
#define BUFFER_SIZE		1024
static unsigned int buffer_length;
static char buffer[BUFFER_SIZE];
static unsigned int buffer_pos;

static int rbuffer_open(struct inode *inode, struct file *file)
{
	/* Just open new sequence with special seq_file operations */
	return seq_open(file, &rbuffer_seq_ops);
}

static ssize_t rbuffer_read(struct file *filp, char __user *buf, size_t size,
			    loff_t *off)
{
	struct seq_file *sf = (struct seq_file *)filp->private_data;

	/* Allocate buffer with requested block size */
	sf->buf = kmalloc((sf->size = size + 1), GFP_KERNEL);

	return seq_read(filp, buf, size, off);
}

static unsigned int *update_pos(loff_t *pos)
{
	int err = 0;

	/* If there is nothing to read already and no time to wait */
	if (*pos && !buffer_length)
		return NULL;
	/* If this is the end of buffer,
	 * and we should hold our breath and count to ten */
	if (*pos >= buffer_length) {
		buffer_pos = 0;
		*pos -= buffer_length;
		buffer_length = 0;
		wake_up_interruptible(&rwbuffer_wq);
	}
	/* Wait for somebody to put data in buffer, if it's empty */
	err = wait_event_interruptible_timeout(rwbuffer_wq,
					       (buffer_length != 0),
					       READ_TIMEOUT);
	/* If timeout elapsed */
	if (err == 0) {
		print_log("Timeout elapsed");
		return NULL;
	}
	/* If something bad happened */
	if (!err)
		return ERR_PTR(err);
	/* Let's read from the last position */
	if (buffer_pos && !*pos)
		*pos = ++buffer_pos;
	/* Remember the current position */
	if (*pos != buffer_pos)
		buffer_pos = *pos;

	return &buffer_pos;
}

static void *rbuffer_seq_start(struct seq_file *sf, loff_t *pos)
{
	return update_pos(pos);
}

static void *rbuffer_seq_next(struct seq_file *sf, void *v, loff_t *pos)
{
	(*pos)++;

	return update_pos(pos);
}

static void rbuffer_seq_stop(struct seq_file *sf, void *p)
{
	return;
}

static int rbuffer_seq_show(struct seq_file *sf, void *p)
{
	return seq_putc(sf, buffer[*((unsigned int *)p)]);
}

static ssize_t wbuffer_write(struct file *file, const char __user *data,
			     size_t len, loff_t *offset)
{
	int write_bytes = 0;
	unsigned long failed_bytes = 0;

	/* Wait for somebody to clean that damn buffer */
	wake_up_interruptible(&rwbuffer_wq);
	wait_event_interruptible(rwbuffer_wq, (buffer_length == 0));
	/* Cleanup buffer */
	memset(buffer, 0x00, BUFFER_SIZE);
	/* Read as much as possible */
	write_bytes = (len > BUFFER_SIZE) ? BUFFER_SIZE : len;
	failed_bytes = copy_from_user(&buffer[buffer_length], data, write_bytes);
	if (failed_bytes) {
		print_error("Couldn't read %lu bytes", failed_bytes);
		print_failed_exit("wbuffer_write");
		return -EFAULT;
	}
	buffer_length += write_bytes;
	*offset += write_bytes;

	return write_bytes;
}

static int wbuffer_release(struct inode *inode, struct file *file)
{
	/* Wake up reading process */
	wake_up_interruptible(&rwbuffer_wq);

	return 0;
}

static int __init rwbuffer_init(void)
{
	print_start("rwbuffer_init");

	/* Create /proc/read */
	proc_read = proc_create(PROC_READ_NAME, 0444, NULL, &rbuffer_fops);
	if (!proc_read) {
		print_error("Couldn't create /proc/%s file", PROC_WRITE_NAME);
		print_failed_exit("rwbuffer_init");
		return -EFAULT;
	}
	/* Create /proc/write */
	proc_write = proc_create(PROC_WRITE_NAME, 0222, NULL, &wbuffer_fops);
	if (!proc_write) {
		print_error("Couldn't create /proc/%s file", PROC_WRITE_NAME);
		print_failed_exit("rwbuffer_init");
		return -EFAULT;
	}
	/* Init wait queue */
	init_waitqueue_head(&rwbuffer_wq);
	print_log("SIZE_T:%lu", SIZE_MAX);

	print_exit("rwbuffer_init");
	return 0;
}

static void __exit rwbuffer_exit(void)
{
	print_start("rwbuffer_exit");

	/* Remove /proc/ files */
	remove_proc_entry(PROC_READ_NAME, NULL);
	remove_proc_entry(PROC_WRITE_NAME, NULL);

	print_exit("rwbuffer_exit");
	return;
}

module_init(rwbuffer_init);
module_exit(rwbuffer_exit);

/***************************************************************/
/************************ Module info **************************/
/***************************************************************/
MODULE_AUTHOR("Petr Nechaev <petr@nechaev.us>");
MODULE_DESCRIPTION("Simple pipe");
MODULE_LICENSE("GPL");
MODULE_VERSION("1.3.2");
